﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using CoreEx.Database.Linq.Translators;
using CoreEx.Database.Metadata;
using CoreEx.Database.Query.Expressions;
using LinFu.IoC.Configuration;
using LinFu.IoC.Interfaces;
using LinFu.IoC;

namespace CoreEx.Database.Linq
{
    /// <summary>
    /// Provides a base class for implementing the <see cref="IQueryTranslator"/> interface.
    /// </summary>
    public abstract class QueryTranslatorBase : IQueryTranslator, IInitialize
    {

        private IEnumerable<IExpressionRewriter> _rewriters;

        /// <summary>
        /// Gets an <see cref="IAliasProvider"/> instance that is responsible 
        /// for providing table aliases.
        /// </summary>
        protected IAliasProvider AliasProvider { get; private set; }

        /// <summary>
        /// Gets an <see cref="ITypeInfoMapper"/> instance that provides
        /// the mapping between types and database tables.
        /// </summary>
        protected ITypeInfoMapper TypeInfoMapper { get; private set; }

        /// <summary>
        /// Gets the <see cref="IServiceContainer"/> that
        /// created this instance.
        /// </summary>
        protected IServiceContainer ServiceContainer { get; private set; }

        /// <summary>
        /// Gets the <see cref="TranslateResult"/> instance containing 
        /// detail information about the current query translation process.
        /// </summary>
        public TranslateResult TranslateResult { get; set; }


        /// <summary>
        /// Gets the <see cref="DbExpressionFactory"/> that is used to create 
        /// new <see cref="DbExpression"/> instances.
        /// </summary>
        protected static DbExpressionFactory ExpressionFactory { get; private set; }


        /// <summary>
        /// Initializes a new instance of the <see cref="QueryTranslatorBase"/> class.
        /// </summary>
        /// <param name="connectionName">The name of the database connection.</param>
        protected QueryTranslatorBase(string connectionName)
        {
            TranslateResult = new TranslateResult {ConnectionName = connectionName};
            ExpressionFactory = new DbExpressionFactory();
        }


        /// <summary>
        /// Translates the <paramref name="expression"/> into a <see cref="TranslateResult"/> instance.
        /// </summary>
        /// <param name="expression">The <see cref="Expression"/> to translate.</param>
        /// <returns>
        /// A <see cref="TranslateResult"/> instance that represents the translated <paramref name="expression"/>
        /// </returns>
        public abstract TranslateResult Translate(Expression expression);

        /// <summary>
        /// Translates the specified expression.
        /// </summary>
        /// <param name="methodCallExpression">The expression.</param>
        /// <param name="outerMappedProperty">The outer persistent member.</param>
        /// <returns></returns>
        public abstract TranslateResult Translate(MethodCallExpression methodCallExpression, MappedProperty outerMappedProperty);
        


        protected DbExpression Visit(Expression expression)
        {
            switch (expression.NodeType)
            {
                case ExpressionType.Negate:
                case ExpressionType.NegateChecked:
                case ExpressionType.Not:
                case ExpressionType.Convert:
                case ExpressionType.ConvertChecked:
                case ExpressionType.ArrayLength:
                case ExpressionType.Quote:
                case ExpressionType.TypeAs:
                    return VisitUnary((UnaryExpression)expression);
                case ExpressionType.Lambda:
                    return VisitLambda((LambdaExpression)expression);
                case ExpressionType.Add:
                case ExpressionType.AddChecked:
                case ExpressionType.Subtract:
                case ExpressionType.SubtractChecked:
                case ExpressionType.Multiply:
                case ExpressionType.MultiplyChecked:
                case ExpressionType.Divide:
                case ExpressionType.Modulo:
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                case ExpressionType.Or:
                case ExpressionType.OrElse:
                case ExpressionType.LessThan:
                case ExpressionType.LessThanOrEqual:
                case ExpressionType.GreaterThan:
                case ExpressionType.GreaterThanOrEqual:
                case ExpressionType.Equal:
                case ExpressionType.NotEqual:
                case ExpressionType.Coalesce:
                case ExpressionType.ArrayIndex:
                case ExpressionType.RightShift:
                case ExpressionType.LeftShift:
                case ExpressionType.ExclusiveOr:
                    return VisitBinary((BinaryExpression)expression);
                case ExpressionType.MemberAccess:
                    return VisitMemberAccess((MemberExpression)expression);
                case ExpressionType.Constant:
                    return VisitConstant((ConstantExpression)expression);
                case ExpressionType.Call:
                    return VisitMethodCall((MethodCallExpression)expression);
                case ExpressionType.Parameter:
                    return VisitParameter((ParameterExpression)expression);
                case ExpressionType.New:
                    return VisitNew((NewExpression)expression);
                case ExpressionType.Conditional:
                    return VisitConditional((ConditionalExpression)expression);
                case ExpressionType.MemberInit:
                    return VisitMemberInit((MemberInitExpression)expression);


            }

            throw new NotSupportedException(expression.ToString());
        }

        protected virtual DbExpression VisitLambda(LambdaExpression expression)
        {
            return Visit(expression.Body);
        }

        protected abstract DbExpression VisitMemberInit(MemberInitExpression memberInitExpression);

        protected abstract DbExpression VisitConditional(ConditionalExpression conditionalExpression);

        protected abstract DbExpression VisitUnary(UnaryExpression expression);

        protected abstract DbExpression VisitMethodCall(MethodCallExpression methodCallExpression);

        protected abstract DbExpression VisitConstant(ConstantExpression expression);

        protected abstract DbExpression VisitMemberAccess(MemberExpression expression);

        protected abstract DbExpression VisitBinary(BinaryExpression expression);

        protected abstract DbExpression VisitParameter(ParameterExpression expression);

        protected abstract DbExpression VisitNew(NewExpression expression);


        protected  Expression Rewrite(Expression expression)
        {                        
            foreach (var rewriter in _rewriters)
                expression = rewriter.Rewrite(expression);
            return expression;
        }


        public virtual void Initialize(IServiceContainer source)
        {
            ServiceContainer = source;
            AliasProvider = ServiceContainer.GetService<IAliasProvider>();
            TypeInfoMapper = ServiceContainer.GetService<ITypeInfoMapper>(string.Empty, TranslateResult.ConnectionName);
            _rewriters = ServiceContainer.GetServices<IExpressionRewriter>().ToList();
        }
    }
}
